오늘 풀어본 문제는 백준의 1647번 문제1이다. 문제 풀이에 사용한 언어는 C++ 이다.
이 문제의 내용과 조건은 다음과 같다.
동물원에서 막 탈출한 원숭이 한 마리가 세상구경을 하고 있다. 그러다가 평화로운 마을에 가게 되었는데, 그곳에서는 알 수 없는 일이 벌어지고 있었다.
마을은 $N$ 개의 집과 그 집들을 연결하는 $M$ 개의 길로 이루어져 있다. 길은 어느 방향으로든지 다닐 수 있는 편리한 길이다. 그리고 각 길마다 길을 유지하는데 드는 유지비가 있다. 임의의 두 집 사이에 경로가 항상 존재한다.
마을의 이장은 마을을 두 개의 분리된 마을로 분할할 계획을 가지고 있다. 마을이 너무 커서 혼자서는 관리할 수 없기 때문이다. 마을을 분할할 때는 각 분리된 마을 안에 집들이 서로 연결되도록 분할해야 한다. 각 분리된 마을 안에 있는 임의의 두 집 사이에 경로가 항상 존재해야 한다는 뜻이다. 마을에는 집이 하나 이상 있어야 한다.
그렇게 마을의 이장은 계획을 세우다가 마을 안에 길이 너무 많다는 생각을 하게 되었다. 일단 분리된 두 마을 사이에 있는 길들은 필요가 없으므로 없앨 수 있다. 그리고 각 분리된 마을 안에서도 임의의 두 집 사이에 경로가 항상 존재하게 하면서 길을 더 없앨 수 있다. 마을의 이장은 위 조건을 만족하도록 길들을 모두 없애고 나머지 길의 유지비의 합을 최소로 하고 싶다. 이것을 구하는 프로그램을 작성하시오.
첫째 줄에 집의 개수 $N$, 길의 개수 $M$ 이 주어진다. $N$ 은 $2$ 이상 $100,000$ 이하인 정수이고, $M$ 은 $1$ 이상 $1,000,000$ 이하인 정수이다. 그 다음 줄부터 $M$ 줄에 걸쳐 길의 정보가 $A\ B\ C$ 세 개의 정수로 주어지는데 $A$ 번 집과 $B$ 번 집을 연결하는 길의 유지비가 $C$ $(1 \le C \le 1,000)$ 라는 뜻이다.
임의의 두 집 사이에 경로가 항상 존재하는 입력만 주어진다.
첫째 줄에 없애고 남은 길 유지비의 합의 최솟값을 출력한다.
문제를 해결하기 위해 사용한 방식은 다음과 같다.
프림 알고리즘을 이용하여 최소 신장 트리를 구한다.
최소 신장 트리를 구하는 과정에서 트리에 포함되는 가장 긴 간선의 가중치를 저장한다.
최소 신장 트리의 가중치의 합에서 가장 긴 간선의 가중치가 정답이 된다.
코드는 다음과 같이 작성하였다.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
struct edge_cmp {
bool operator()(const pii& A, const pii& B) {
return A.second > B.second;
}
};
ll prim (const vector<vector<pii>>& graph, int& maximum);
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int N, M;
cin >> N >> M;
vector<vector<pii>> graph(N+1);
for (int i=0; i<M; i++) {
int A, B, C;
cin >> A >> B >> C;
graph[A].emplace_back(make_pair(B, C));
graph[B].emplace_back(make_pair(A, C));
}
int maximum = 0;
ll result = prim(graph, maximum);
cout << result - maximum;
return 0;
}
ll prim (const vector<vector<pii>>& graph, int& maximum) {
size_t n = graph.size() - 1;
priority_queue<pii, vector<pii>, edge_cmp> edges;
vector<bool> visited(n+1, false);
ll result = 0;
edges.emplace(1, 0);
for(int i=0; i<n; i++) {
pii curr;
do {
curr = edges.top();
edges.pop();
} while (visited[curr.first]);
visited[curr.first] = true;
result += curr.second;
maximum = max(maximum, curr.second);
for (auto& next : graph[curr.first]) {
if (!visited[next.first]) {
edges.push(next);
}
}
}
return result;
}
그러자 모든 테스트 케이스를 통과하고 ‘맞았습니다’가 나오는 것을 확인할 수 있었다.
어제도 잠깐 이야기 했지만, 연휴가 시작되면서 금요일부터는 코딩이 힘들 것 같아 오늘 한 문제를 더 풀게 되었다. 다행히 아까 풀었던 프림 알고리즘을 또 사용하는 문제라 이번에는 비교적 쉽게 풀렸던 것 같다.
앞으로 남은 문제는 두 문제이고, 내일 모두 해결하여 CLASS 5 금색 휘장을 얻을 계획이다. 이제 비교적 쉬운(?) 문제들만 남았으니 아마 해낼 수 있을 거라고 생각한다.
오늘의 PS는 여기까지!
1: https://www.acmicpc.net/problem/1647